#include "Window.h"
#include "glad/glad.h"
#include "GLFW/glfw3.h"
#include <iostream>
namespace kptv3
{
    unsigned int Window::m_GLFWWindowCount = 0;

    Window::Window(WindowConfig w_config)
        : m_winConf(std::make_shared<WindowConfig>(w_config))
    {
        if (m_GLFWWindowCount == 0) {
            int success = glfwInit();
            if (!success)
            {
                std::cerr << "Could not initialize GLFW!\n";
                assert(false);
            }
            glfwSetErrorCallback([](int erCode, const char *description) {
                    std::cerr << "GLFW Error (" << erCode << "): " 
                            << description << "\n";
            });

            glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
            glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
            glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef BR_APPLE
            glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

            m_GLFWWindowCount++;
        }

        {
            int monitorCount;
            auto monitor = glfwGetMonitors(&monitorCount);
            //  Use the first monitor in default
            m_CurMonitor = monitor[0];
            if (monitorCount > 0) {
                m_pVideoMode = (GLFWvidmode *) glfwGetVideoMode(monitor[0]);
                m_Monitors.reserve(monitorCount);
                m_Monitors.assign(monitor, monitor + monitorCount);
            }
        }

        {
            if (m_winConf->full_screen) {
                m_winConf->width = m_pVideoMode->width;
                m_winConf->height = m_pVideoMode->height;
                m_Window = glfwCreateWindow(m_pVideoMode->width, m_pVideoMode->height, m_winConf->title.c_str(),
                                            m_CurMonitor, NULL);
            } else {
                m_Window = glfwCreateWindow((int) m_winConf->width, (int) m_winConf->height, m_winConf->title.c_str(),
                                            NULL, NULL);
            }
        }


        if (nullptr == m_Window) {
            // CHECK(m_Window) << "Failed to create GLFW window";
            if(nullptr == m_Window){
                std::cerr << "Failed to create GLFW window\n";
                assert(false);
            }
            glfwTerminate();
        } else {
            glfwMakeContextCurrent(m_Window);
            if(!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {
                std::cerr << "Failed to initialize GLAD\n";
                assert(false);
            }

            m_WinData = std::make_shared<WindowData>();

            // Set GLFW callbacks
            glfwSetWindowSizeCallback(m_Window, [](GLFWwindow *window, int width, int height) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);

                for (size_t i = 0; i < data.m_winSizeFuncs.size(); i++)
                {
                    data.m_winSizeFuncs[i](width, height);
                }
                
            });

            glfwSetWindowCloseCallback(m_Window, [](GLFWwindow *window) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);
                
                for (size_t i = 0; i < data.m_winCloseFuncs.size(); i++)
                {
                    data.m_winCloseFuncs[i]();
                }
                
            });

            glfwSetWindowFocusCallback(m_Window, [](GLFWwindow *window, int isFocus) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);
                
                for (size_t i = 0; i < data.m_winFocusFuncs.size(); i++)
                {
                    data.m_winFocusFuncs[i](isFocus == GLFW_TRUE);
                }
                
            });

            glfwSetKeyCallback(m_Window, [](GLFWwindow *window, int key, int scancode, int action, int mods) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);
                
                switch (action) {
                    case GLFW_PRESS: {
                        for (size_t i = 0; i < data.m_keyPressedFuncs.size(); i++)
                        {
                            data.m_keyPressedFuncs[i](key);
                        }
                        break;
                    }
                    case GLFW_RELEASE: {
                        for (size_t i = 0; i < data.m_keyReleasedFuncs.size(); i++)
                        {
                            data.m_keyReleasedFuncs[i](key);
                        }
                        break;
                    }
                    case GLFW_REPEAT: {
                        for (size_t i = 0; i < data.m_keyRepeatedFuncs.size(); i++)
                        {
                            data.m_keyRepeatedFuncs[i](key);
                        }
                    }
                    default:
                        break;
                }
            });

            glfwSetCharCallback(m_Window, [](GLFWwindow *window, unsigned int keycode) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);

                for (size_t i = 0; i < data.m_charFuncs.size(); i++)
                {
                    data.m_charFuncs[i](keycode);
                }        
            });

            glfwSetMouseButtonCallback(m_Window, [](GLFWwindow *window, int button, int action, int mods) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);

                switch (action) {
                    case GLFW_PRESS: {
                        for (size_t i = 0; i < data.m_mousePressedFuncs.size(); i++)
                        {
                            data.m_mousePressedFuncs[i](button);
                        }
                        break;
                    }
                    case GLFW_RELEASE:
                    default:
                        for (size_t i = 0; i < data.m_mouseReleasedFuncs.size(); i++)
                        {
                            data.m_mouseReleasedFuncs[i](button);
                        }
                        break;
                }
            });

            glfwSetScrollCallback(m_Window, [](GLFWwindow *window, double xOffset, double yOffset) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);

                for (size_t i = 0; i < data.m_scrollFuncs.size(); i++)
                {
                    data.m_scrollFuncs[i](xOffset, yOffset);
                }
            });

            glfwSetCursorPosCallback(m_Window, [](GLFWwindow *window, double xPos, double yPos) {
                WindowData &data = *(WindowData *) glfwGetWindowUserPointer(window);

                for (size_t i = 0; i < data.m_cursorPosFuncs.size(); i++)
                {
                    data.m_cursorPosFuncs[i](xPos, yPos);
                }
            });

            glfwSetWindowUserPointer(m_Window, m_WinData.get());

        }

    }

    Window::~Window()
    {
        glfwDestroyWindow(m_Window);
    }

    void Window::Display()
    {
        glfwSwapBuffers(m_Window);
        glfwPollEvents();
    }

} // namespace brick
